<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>操纵节点</title>
</head>
<body>
  <div id="app">
    <div class="content">
      <p>Hello world 111111</p>
      <p>Hello Dom   222222</p>
    </div>
    <p class="footer">我是底部信息</p>
  </div>
  <ul id="ul" onclick="handleClone()">
    <li class="item">内容1</li>
    <li class="item">内容2</li>
    <li class="item">内容3</li>
  </ul>
</body>
<script>
  /** 1.appendChild()
   *    a.因为所有关系指针都是只读的，所以 DOM 又提供了一些操纵节点的方法。
   *    b.最常用的方法是appendChild()，用于在 childNodes 列表末尾添加节点。添加新节点会更新相关的关系指针，包括父节点和之前的最后一个子节点。
   *      appendChild()方法返回新添加的节点。
   *    c.如果把文档中已经存在的节点传给 appendChild()，则这个节点会从之前的位置被转移到新位置。即使 DOM 树通过各种关系指针维系，一个节点也不
   *      会在文档中同时出现在两个或更多个地方。因此，如果调用 appendChild()传入父元素的第一个子节点，则这个节点会成为父元素的最后一个子节点。
   * **/
   function verificateAppendChild(){
     //1.创建一个节点
     let dom = document.createElement('div');
     dom.innerHTML = '我是被创造出来的节点';
     dom.setAttribute('id','create');
     dom.style.color='red';
     //2.将创建的节点添加到父节点的末尾
     let father = document.getElementById('app');
     let newNode = father.appendChild(dom);
     console.log('newNode === father.lastChild:',newNode === father.lastChild);//newNode === father.lastChild: true
     //3.获取第二个节点并将其添加到列表末尾
     console.log(father.childNodes);
     let secondNode = father.appendChild(father.childNodes[1]);
     console.log(secondNode === father.lastChild);//true
   }
  //  verificateAppendChild();

   /**2.insertBefore()
    *   a.如果想把节点放到 childNodes 中的特定位置而不是末尾，则可以使用 insertBefore()方法。这个方法接收两个参数：要插入的节点和参照节点。
    *     调用这个方法后，要插入的节点会变成参照节点的前一个同胞节点，并被返回。如果参照节点是 null，则 insertBefore()与 appendChild()效
    *     果相同。
    * **/
   function verificateInsertBefore(){
     //1.创建一个节点
     let dom = document.createElement('div');
     dom.innerHTML = '<a href="https://www.baidu.com">我是被创建的第二个元素</a>';
     let father = document.getElementById('app');
     //2.作为最后一个子节点插入
     father.insertBefore(dom,null);
     console.log('father.lastChild === dom',father.lastChild === dom);//father.lastChild === dom true
     //3.作为新的第一个子节点插入
     father.insertBefore(dom,father.firstChild);
     console.log('father.firstChild === dom',father.firstChild === dom);//father.firstChild === dom true
     //4.插入最后一个子节点前面
     father.insertBefore(dom,father.lastChild);
     console.log('father.childNodes[father.childNodes.length - 2] === dom',father.childNodes[father.childNodes.length - 2] === dom);//father.childNodes[father.childNodes.length - 2] === dom true
   }
  //  verificateInsertBefore();

   /**3.replaceChild()
    *   a.appendChild() 和 insertBefore() 在插入节点时不会删除任何已有节点。
    *   b.相对地，replaceChild()方法接收两个参数：要插入的节点和要替换的节点。要替换的节点会被返回并从文档树中完全移除，要插入的节点会取而代之。
    *   c.使用 replaceChild() 插入一个节点后，所有关系指针都会从被替换的节点复制过来。虽然被替换的节点从技术上说仍然被同一个文档所拥有，但文档中
    *     已经没有它的位置。
    * **/
    function verificateReplaceChild(){
      //1.新建节点
      let dom = document.createElement('div');
      dom.innerText = '<a href="https://www.baidu.com">我是要替换其他标签的标签元素</a>';
      dom.style.color = 'orange';
      let father = document.getElementById('app');
      //2.替换第一个子节点
      let returnFirstNode = father.replaceChild(dom,father.firstChild);
      console.log('father.firstChild === dom',father.firstChild === dom);//father.firstChild === dom true
      //3.替换最后一个子节点
      let returnLastNode = father.replaceChild(dom,father.lastChild);
      console.log('father.lastChild === dom',father.lastChild === dom);//father.lastChild === dom true
    }
    // verificateReplaceChild();

    /**4.removeChild()
     *   a.要移除节点而不是替换节点，可以使用 removeChild() 方法。这个方法接收一个参数，即要移除的节点。被移除的节点会被返回。
     *   b.replaceChild()方法一样，通过 removeChild() 被移除的节点从技术上说仍然被同一个文档所拥有，但文档中已经没有它的位置。
     * **/
    function verificateRemoveChild(){
      let father = document.getElementById('app');
      // 删除第一个子节点
      console.log(father.childNodes);
      let fromFirstChild = father.removeChild(father.firstChild);
      console.log(father.childNodes);
      console.log('fromFirstChild:',fromFirstChild);
      // 删除最后一个子节点
      let fromLastChild = father.removeChild(father.lastChild);
      console.log(father.childNodes);
    }
    // verificateRemoveChild();

    // 上面介绍的 4 个方法都用于操纵某个节点的子元素，也就是说使用它们之前必须先取得父节点（使
    // 用前面介绍的 parentNode 属性）。并非所有节点类型都有子节点，如果在不支持子节点的节点上调用
    // 这些方法，则会导致抛出错误。

    /**5.其他方法-cloneNode（参考MDN）
     *   a.所有节点类型还共享了两个方法。第一个是 cloneNode()，会返回与调用它的节点一模一样的节点。
     *   b.cloneNode()方法接收一个布尔值参数，表示是否深复制。在传入 true 参数时，会进行深复制，即复制节点及其整个子 DOM 树。如果传入 false，
     *     则只会复制调用该方法的节点。(为了适配，调用API的时候，要传入值，因为默认值在不同的浏览器中可能会不同)。（是否采用深度克隆,如果为true,
     *     则该节点的所有后代节点也都会被克隆,如果为false,则只克隆该节点本身.）
     *   c.复制返回的节点属于文档所有，但尚未指定父节点，所以可称为孤儿节点（orphan）。可以通过 appendChild()、insertBefore()或 
     *     replaceChild() 方法把孤儿节点添加到文档中。
     *   d.克隆一个元素节点会拷贝它所有的属性以及属性值,当然也就包括了属性上绑定的事件(比如onclick="alert(1)"),但不会拷贝那些使用addEventListener()
     *     方法或者node.onclick = fn这种用JavaScript动态绑定的事件.
     *   e.在使用Node.appendChild()或其他类似的方法将拷贝的节点添加到文档中之前,那个拷贝节点并不属于当前文档树的一部分,也就是说,它没有父节点.
     *   f.如果deep参数设为false,则不克隆它的任何子节点.该节点所包含的所有文本也不会被克隆,因为文本本身也是一个或多个的Text节点.
     *   g.如果deep参数设为true,则会复制整棵DOM子树(包括那些可能存在的Text子节点).对于空结点(例如<img>和<input>元素),则deep参数无论设为true还是设
     *     为false,都没有关系,但是仍然需要为它指定一个值。
     *   h.注意:为了防止一个文档中出现两个ID重复的元素,使用cloneNode()方法克隆的节点在需要时应该指定另外一个与原ID值不同的ID。
     *   i.如果原始节点设置了ID，并且克隆节点会被插入到相同的文档中，那么应该更新克隆节点的ID以保证唯一性。name属性可能也需要进行修改，取决于你是否希望有
     *     相同名称的节点存在于文档中。
     *   j.想要克隆一个节点来添加到另外一个文档中,请使用Document.importNode()代替本方法。
     * 
     * **/

    function verificateCloneNode(){
      let dom = document.getElementById('ul');
      //1.深复制
      let deepCloneNode = dom.cloneNode(true);
      console.log(dom === deepCloneNode);//false
      console.log(deepCloneNode.childNodes);//NodeList(7) [text, li.item, text, li.item, text, li.item, text]
      //2.将深复制对象添加到父类的末尾
      let father = document.getElementById('app');
      deepCloneNode.setAttribute('id','ul1');//复制的节点ID尽量不要一致 
      father.appendChild(deepCloneNode);
      //3.浅复制
      let shallowCloneNode = dom.cloneNode(false);
      console.log(dom === shallowCloneNode);//false
      console.log(shallowCloneNode.childNodes);//NodeList []length: 0[[Prototype]]: NodeList
    };
    // verificateCloneNode();
    //在这个例子中，deepCloneNode 保存着 dom 的副本。这意味着 deepList 有 3 个列表项，每个列表项又各自包含文本,因此有7个子节点。变量 shallowCloneNode 
    //则保存着 dom 的浅副本，因此没有子节点。deepList.childNodes.length 的值会因 IE8 及更低版本和其他浏览器对空格的处理方式而不同。IE9之前的版本不会为空格创建节点。
    
    function handleClone(){
      console.log('handleClone---');
    }

    /**6.其他方法-normalize()
     *   a.这个方法唯一的任务就是处理文档子树中的文本节点。
     *   b.由于解析器实现的差异或 DOM 操作等原因，可能会出现并不包含文本的文本节点，或者文本节点之间互为同胞关系。在节点上调用 normalize() 方法会检测这个
     *     节点的所有后代，从中搜索上述两种情形。如果发现空文本节点，则将其删除；如果两个同胞节点是相邻的，则将其合并为一个文本节点。
     *   c.Node.normalize() 方法将当前节点和它的后代节点”规范化“（normalized）。在一个"规范化"后的DOM树中，不存在一个空的文本节点，或者两个相邻的文本
     *     节点。
     *   d.注1：“空的文本节点”并不包括空白字符(空格，换行等)构成的文本节点。
     *     注2：两个以上相邻文本节点的产生原因包括：
     *         1.通过脚本调用有关的DOM接口进行了文本节点的插入和分割等。
     *         2.HTML中超长的文本节点会被浏览器自动分割为多个相邻文本节点。
     * **/
     function verificateNormalize(){
       //1.创建一个元素
       let wrapper = document.createElement('div');
       //2.给这个元素添加两个文本节点
       wrapper.appendChild(document.createTextNode('Part 1'));
       wrapper.appendChild(document.createTextNode('Part 2'));
       console.log(wrapper.childNodes.length);//2
       console.log(wrapper.childNodes[0].textContent);//Part 1
       console.log(wrapper.childNodes[1].textContent);//Part 2
       //3.采用normalize，格式化当前节点以及后代节点的文本节点
       wrapper.normalize();
       console.log(wrapper.childNodes[0].textContent);//Part 1Part 2
       console.log(wrapper.childNodes[1]);//undefined
     }
    //  verificateNormalize();

</script>
</html>
